#pragma once
#include "../IInputDevice.h"
#include "Win32Utils.h"
#include "CustomHidController.h"

#include "readerwriterqueue.h"

#include <Windows.h>
#include <hidsdi.h>
// wil should be included after system API headers
#include "wil/resource.h"

#include <array>
#include <cstdlib>
#include <memory>
#include <type_traits>
#include <unordered_map>


namespace TLAC::Input
{
	struct WindowMessageData
	{
		WPARAM wParam;
		LPARAM lParam;
	};

	/* Top level Raw Input IInputDevice class.
	* This class is responsible for handling WM_INPUT and WM_INPUT_DEVICE_CHANGE messages,
	* and dispatching messages to Handlers.
	* Handler methods are only called from within PollInput(), so Handlers don't have to
	* worry about thread safety.
	* 
	* Workflow:
	* RegisterWMInput() is called on initialization. Afterwards:
	* HandleInputMessage() and HandleInputDeviceChangeMessage() may be called any time from
	* other threads.
	* In each tick, PollInput() is called once, then the input states are accessed.
	*/
	class RawInput : public IInputDevice
	{
		friend class RawInputCustomHidControllerBinding;
	public:
		/* Get reference to singleton instance.
		* Note different signature from other components: this method returns a reference, not pointer.
		*/
		static RawInput& GetInstance() {
			static RawInput instance;
			return instance;
		}

		/* Tick update.
		* On each tick, do the following:
		* Call BeginTickMessages for each Handler.
		* For any WM_INPUT_DEVICE_CHANGE messages received, call HandleDeviceArrival or HandleDeviceRemoval
		* for each Handler.
		* For any WM_INPUT messages received, call HandleInput for each Handler.
		* Finally, call EndTickMessages for each Handler.
		*/
		bool PollInput() override;

		/* If raw slider updates is available from any controller, return a pointer to one of them. Otherwise,
		* return nullptr.
		* The pointer is only valid until the next call to PollInput(). Pointer ownership is not transferred
		* to the caller.
		*/
		const std::vector<std::array<std::uint32_t, 32>>* GetSliderUpdates() const;

		// ===== Methods below are thread safe =====
		bool RegisterWMInput(HWND hwnd);
		//bool UnregisterWMInput();
		//void GetCurrentDevices();

		LPARAM HandleInputMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
		LPARAM HandleInputDeviceChangeMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
		// ===== Methods above are thread safe =====

	private:
		RawInput() = default;
		~RawInput() = default;

		moodycamel::ReaderWriterQueue<RawInputDetails::unique_malloc_ptr<RAWINPUT>> input_message_queue_;
		moodycamel::ReaderWriterQueue<WindowMessageData> device_change_message_queue_;

		RawInputCustomHidController::CustomHidControllerHandler hid_controller_handler_;
	};

	
}
